home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 20 / Cream of the Crop 20 (Terry Blount) (1996).iso / faq / cppnl010.zip / CPPNL010.TXT next >
Text File  |  1996-05-07  |  17KB  |  518 lines

  1. Issue #010
  2. May, 1996
  3.  
  4.  
  5. Contents:
  6.  
  7. Introduction to Templates Part 2 - Class Templates
  8. Introduction to Stream I/O Part 5 - Streambuf
  9. Using C++ as a Better C Part 10 - General Initializers
  10. Performance - Per-class New/Delete
  11.  
  12.  
  13. INTRODUCTION TO TEMPLATES PART 2 - CLASS TEMPLATES
  14.  
  15. To continue our introduction of C++ templates, we'll be saying a few
  16. things about class templates in this issue.  Templates are a part of
  17. the language still undergoing major changes, and it's tricky to figure
  18. out just what to say.  But we'll cover some basics that are well
  19. accepted and in current usage.
  20.  
  21. A skeleton for a class template, and definitions of a member function
  22. and a static data item, looks like this:
  23.  
  24.         template <class T> class A {
  25.                 void f();
  26.                 static T x;
  27.         };
  28.  
  29.         template <class T> void A<T>::f()
  30.         {
  31.                 // stuff
  32.         }
  33.  
  34.         template <class T> T A<T>::x = 0;
  35.  
  36. T is a placeholder for a template type argument, and is bound to that
  37. argument when the template is instantiated.  For example, if I say:
  38.  
  39.         A<double> a;
  40.  
  41. then the type value of T is "double".  The binding of template
  42. arguments and the generation of an actual class from a template is a
  43. process known as "instantiation".  You can view a template as a
  44. skeleton or macro or framework.  When specific types, such as double,
  45. are added to this skeleton, the result is an actual C++ class.
  46.  
  47. Template arguments may also be constant expressions:
  48.  
  49.         template <int N> struct A {
  50.                 // stuff
  51.         };
  52.  
  53.         A<-37> a;
  54.  
  55. This feature is useful in the case where you want to pass a size into
  56. the template.  For example, a Vector template might accept a type
  57. argument that tells what type of elements will be operated on, and a
  58. size argument giving the vector length:
  59.  
  60.         template <class T, int N> class Vector {
  61.                 // stuff
  62.         };
  63.  
  64.         Vector<float, 100> v;
  65.  
  66. A template argument may have a default specified (this feature is not
  67. widely available as yet):
  68.  
  69.         template <class T = int, int N = 100> class Vector {
  70.                 // stuff
  71.         };
  72.  
  73.         Vector<float, 50> v1;           // Vector<float, 50>
  74.         Vector<char> v2;                // Vector<char, 100>
  75.         Vector<> v3;                    // Vector<int, 100>
  76.  
  77. To see how some of these basic ideas fit together, let's actually
  78. build a simple Vector template, with set() and get() functions:
  79.  
  80.         template <class T, int N = 100> class Vector {
  81.                 T vec[N];
  82.         public:
  83.                 void set(int pos, T val);
  84.                 T get(int pos);
  85.         };
  86.  
  87.         template <class T, int N> void Vector<T, N>::set(int pos, T val)
  88.         {
  89.                 if (pos < 0 || pos >= N)
  90.                         ; // give error of some kind
  91.  
  92.                 vec[pos] = val;
  93.         }
  94.  
  95.         template <class T, int N> T Vector<T, N>::get(int pos)
  96.         {
  97.                 if (pos < 0 || pos >= N)
  98.                         ; // give error of some kind
  99.  
  100.                 return vec[pos];
  101.         }
  102.  
  103.         // driver program
  104.  
  105.         int main()
  106.         {
  107.                 Vector<double, 10> v;
  108.                 int i = 0;
  109.                 double d = 0.0;
  110.  
  111.                 // set locations in vector
  112.  
  113.                 for (i = 0; i < 10; i++)
  114.                         v.set(i, double(i * i));
  115.  
  116.                 // get location values from vector
  117.  
  118.                 for (i = 0; i < 10; i++)
  119.                         d = v.get(i);
  120.  
  121.                 return 0;
  122.         }
  123.  
  124. Actual values are stored in a private vector of type T and length N.
  125. In a real Vector class we might overload operator[] to provide a
  126. natural sort of interface such as an actual vector has.
  127.  
  128. What would happen if we said something like:
  129.  
  130.         Vector<char, -1000> v;
  131.  
  132. This is an example of code that is legal until the template is
  133. actually instantiated into a class.  Because a member like:
  134.  
  135.         char vec[-1000];
  136.  
  137. is not valid (you can't have arrays of negative or zero size), this
  138. usage will be flagged as an error when instantiation is done.
  139.  
  140. The process of instantiation itself is a bit tricky.  If I have 10
  141. translation units (source files), and each uses an instantiated class:
  142.  
  143.         Vector<unsigned long, 250>
  144.  
  145. where does the code for the instantiated class's member functions go?
  146. The template definition itself resides most commonly in a header file,
  147. so that it can be accessed everywhere and because template code has
  148. some different properties than other source code.
  149.  
  150. This is an extremely hard problem for a compiler to solve.  One
  151. solution is to make all template functions inline and duplicate the
  152. code for them per translation unit.  This results in very fast but
  153. potentially bulky code.
  154.  
  155. Another approach, which works if you have control over the object file
  156. format and the linker, is to generate duplicate instantiations per
  157. object file and then use the linker to merge them.
  158.  
  159. Yet another approach is to create auxiliary files or directories
  160. ("repositories") that have a memory of what has been instantiated in
  161. which object file, and use that state file in conjunction with the
  162. compiler and linker to control the instantiation process.
  163.  
  164. There are also schemes for explicitly forcing instantiation to take
  165. place.  We'll discuss these in a future issue.  The instantiation
  166. issue is usually hidden from a programmer, but sometimes becomes
  167. visible, for example if the programmer notices that object file sizes
  168. seem bloated.
  169.  
  170.  
  171. INTRODUCTION TO STREAM I/O PART 5 - STREAMBUF
  172.  
  173. In previous issues we talked about various ways of copying files using
  174. stream I/O, some of the ways of affecting I/O operations by specifying
  175. unit buffering or not and tying one stream to another, and so on.
  176.  
  177. Another way of copying input to output using stream I/O is to say:
  178.  
  179.         #include <iostream.h>
  180.  
  181.         int main()
  182.         {
  183.                 int c;
  184.  
  185.                 while ((c = cin.rdbuf()->sbumpc()) != EOF)
  186.                         cout.rdbuf()->sputc(c);
  187.  
  188.                 return 0;
  189.         }
  190.  
  191. This scheme uses what are known as streambufs, underlying buffers used
  192. in the stream I/O package.  An expression:
  193.  
  194.         cin.rdbuf()->sbumpc()
  195.  
  196. says "obtain the streambuf pointer for the standard input stream
  197. (cin) and grab the next character from it and then advance the
  198. internal pointer within the buffer".  Similarly,
  199.  
  200.         cout.rdbuf()->sputc(c)
  201.  
  202. adds a character to the output buffer.
  203.  
  204. Doing I/O in this way is lower-level than some other approaches, but
  205. correspondingly faster.  If we summarize the four file-copying methods
  206. we've studied (see issues #008 and #009 for code examples of them),
  207. from slowest to fastest, they might be as follows.
  208.  
  209. Copy a character at a time with >> and <<:
  210.  
  211.         cin.tie(0);
  212.         cin.unsetf(ios::skipws);
  213.  
  214.         while (cin >> c)
  215.                 cout << c;
  216.  
  217. Copy using get() and put():
  218.  
  219.         ifstream ifs(argv[1], ios::in | ios::binary);
  220.         ofstream ofs(argv[2], ios::out | ios::binary);
  221.  
  222.         while (ifs.get(c))
  223.                 ofs.put(c);
  224.  
  225. Copy with streambufs (above):
  226.  
  227.         while ((c = cin.rdbuf()->sbumpc()) != EOF)
  228.                 cout.rdbuf()->sputc(c);
  229.  
  230. Copy with streambufs but explicit copying buried:
  231.  
  232.         ifstream ifs(argv[1], ios::in | ios::binary);
  233.         ofstream ofs(argv[2], ios::out | ios::binary);
  234.  
  235.         ofs << ifs.rdbuf();
  236.  
  237. A table of relative times, for one popular C++ compiler, comes out
  238. like so:
  239.  
  240.         >>, <<                  100
  241.  
  242.         get/put                 72
  243.  
  244.         streambuf               62
  245.  
  246.         streambuf hidden        43
  247.  
  248. Actual times will vary for a given library.  Perhaps the most critical
  249. factor is whether functions that are used in a given case are inlined
  250. or not.  Note also that if you are copying binary files you need to be
  251. careful with the way copying is done.
  252.  
  253. Why the time differences?  All of these methods use streambufs in some
  254. form.  But the slowest method, using >> and <<, also does additional
  255. pro